
local alien = require 'alien'
local alienex = require 'alienex'
local struct = require 'alien.struct'
local config = require 'config'
local debug = require 'debug'
local bit = require 'bit'
local setmetatable, ipairs, pcall, type, assert, pairs, print, xpcall, require, error, tostring
    = setmetatable, ipairs, pcall, type, assert, pairs, print, xpcall, require, error, tostring
local strmatch, strbyte, strlower
    = string.match, string.byte, string.lower
local io_open
    = io.open
local max, floor
    = math.max, math.floor


module 'imageio'


-- image format handler registration
do

  local max_magic_bytes = 0

  local handlers = {}

  function add_handler(handler)
    max_magic_bytes = max(max_magic_bytes, #handler.pattern)
    handlers[#handlers+1] = handler
  end
  
  -- reader object

  local reader_proto = {}
  local reader_meta = {__index = reader_proto}

  -- intended for use by image file format handlers
  function create_reader(def)
    return setmetatable(def, reader_meta)
  end

  -- reader functions

  function readfile(path)
    local f = io_open(path, 'rb')
    if not f then
      return nil, 'file not found'
    end
    local magic = f:read(max_magic_bytes) or ''
    
    f:seek('set', 0)
    for i, handler in ipairs(handlers) do
      if strmatch(magic, '^' .. handler.pattern) then
        local success, result = xpcall(function()  return handler.reader(f, path);  end, function(err) return debug.traceback(err or '<nil>', 2);  end)
        if success then
          return result
        else
          f:close()
          return nil, result
        end
      end
    end
    f:close()
    return nil, 'unrecognised image file format'
  end

end

grays = {}
for i = 1,256 do
  local v = (i-1) / 255
  grays[i] = {r=v, g=v, b=v, a=1}
end

do

  local pixmap_proto = {}
  local pixmap_meta = {__index = pixmap_proto}
  
  function createpixmap(width, height, pitch, format, palette)
    return setmetatable({
      buffer = alien.buffer(pitch * height);
      pitch = pitch;
      width = width;
      palette = palette;
      height = height;
      format = format;
    }, pixmap_meta)
  end

  function pixmap(input)
    if (type(input) == 'string') then
      input = assert(readfile(input))
    end
    local pitch = input.pitch
    local width = input.width
    local height = input.height
    local format = input.format
    local palette = input.palette
    local buffer = alien.buffer(pitch * height)
    while true do
      local start_row, row_count, data = input:getdata()
      if (start_row == nil) then
        break
      end
      local dest_offset = pitch * (start_row-1)
      local data_len = pitch * row_count
      if type(data) == 'string' then
        for i = 1, data_len do
          buffer:set(dest_offset + i, strbyte(data,i), 'char')
        end
      else
        data = alien.buffer(data)
        for i = 1, data_len do
          buffer:set(dest_offset + i, data:get(i, 'char'), 'char')
        end
      end
    end
    return setmetatable({
      buffer = buffer;
      pitch = pitch;
      width = width;
      palette = palette;
      height = height;
      format = format;
    }, pixmap_meta)
  end
  
  local savers = {}
  local savers_by_extension = {}
  
  function add_saver(saver)
    savers[saver.name] = saver
    local extensions = saver.extensions
    if extensions then
      for i,extension in ipairs(extensions) do
        savers_by_extension[extension] = saver
      end
    end
  end
  
  function pixmap_proto:row_pointers()
    local boxed_ptr = alien.buffer(alien.sizeof('pointer'))
    boxed_ptr:set(1, self.buffer, 'pointer')
    local buffer_ptr_num = boxed_ptr:get(1, alienex.size_t)
    
    local row_pointers = alien.array(alienex.size_t, self.height)
    for i = 1, self.height do
      row_pointers[i] = buffer_ptr_num + (i-1) * self.pitch
    end
    row_pointers.type = 'pointer'
    return row_pointers
  end
  
  function pixmap_proto:read(formats, backwards)
    local row_pointers = self:row_pointers()
    if backwards then
      for i = self.height, 1, -1 do
        
      end
    else
      for i = 1, self.height do
      end
    end
  end
  
  function pixmap_proto:save(path, encoding)
    local saver
    if encoding then
      saver = savers[encoding]
    else
      local extension = strlower(strmatch(path, '%.([^%.%/%\\]+)$'))
      saver = savers_by_extension[extension]
    end
    if not saver then
      return false, 'unrecognised image format'
    end
    local f, problem = io_open(path, 'wb')
    if not f then
      return false, problem
    end
    local success, result = pcall(saver.save, self, f)
    f:close()
    if not success then
      return false, result
    end
    return true
  end
  
  function pixmap_proto:changeformat(new_format)
    if (self.format == 'p8') and (new_format == 'r8g8b8a8') then
      local w, h = self.width, self.height
      local old_bpp = 1
      local old_pitch = self.pitch
      local new_bpp = 4
      local new_pitch = w * new_bpp
      local new_pixmap = createpixmap(w, h, new_pitch, new_format)
      local palette_codes = {}
      for i,p in ipairs(self.palette) do
        palette_codes[i-1] = bit.bor(bit.lshift(floor(p.r * 255), 24), bit.lshift(floor(p.g * 255), 16), bit.lshift(floor(p.b * 255), 8), floor(p.a * 255))
      end
      for j = 0, h-1 do
        for i = 0, w-1 do
          local c = self.buffer:get(1 + (j * old_pitch) + (i * old_bpp), 'char')
          if (c < 0) then
            c = 256 + c
          end
          c = palette_codes[c]
          new_pixmap.buffer:set(1 + (j * new_pitch) + (i * new_bpp), c, 'uint')
        end
      end
      return new_pixmap
    else
      error('unsupported format transformation: ' .. tostring(self.format) .. '>' .. new_format)
    end
  end

end

--[==[
local buffer_proto = {}
local buffer_meta = {__index = buffer_proto}

function create(fmt, w, h, pitch)
  pitch = pitch or (w * #fmt)
  local buffer = alien.buffer(pitch * h)
  return setmetatable({pitch=pitch, width=w, height=h, buffer=buf, format=fmt}, buffer_meta)
end
--]==]

local imageio_formats = config.imageio_formats
if imageio_formats then
  for i,format_name in ipairs(imageio_formats) do
    require((...).."."..format_name)
  end
end
